package org.example.config;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Pointcut;
import org.example.aop.ProjectAdvice;
import org.example.bean.InfoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

@Configuration
//如果配置了自定义的属性类，需要通过该注解引入
@EnableConfigurationProperties(value = {InfoProperties.class})
@Slf4j
public class EnableAutoAOPConfiguration {


    @Resource
    private InfoProperties infoProperties;

    /**
     * 实现对于字节码文件的修改，其中主要是针对我们定义好的aop类动态添加@Pointcut注解 以及@Around注解
     * @return
     * @throws Exception
     */
    @Bean
    public ProjectAdvice projectAdvice() throws Exception{
        ClassPool aDefault = ClassPool.getDefault();
        //使用类的全类名
        CtClass ctClass = aDefault.get("org.example.aop.ProjectAdvice");
        //输入方法名获取方法对象
        CtMethod ctMethod = ctClass.getDeclaredMethod("aspect");
        //方法示例
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        //常量池
        ConstPool constPool = methodInfo.getConstPool();
        //新增切面并绑定到对应的字节码的aspect()的方法上
        saveAnnotation(constPool, methodInfo, "org.aspectj.lang.annotation.Pointcut", "value",
                infoProperties.getExecutor());
        CtMethod around = ctClass.getDeclaredMethod("aroundAdvice");
        MethodInfo aroundMethodInfo = around.getMethodInfo();
        ConstPool aroundMethodInfoConstPool = aroundMethodInfo.getConstPool();
        //设置环绕切面绑定到对应的字节码的aroundAdvice()的方法上
        saveAnnotation(aroundMethodInfoConstPool, aroundMethodInfo, "org.aspectj.lang.annotation.Around",
                "value", "aspect()");  //@Around(value="aspect()")
        //要把结果塞入到对应的字节码的位置：target中，但是target在实际中生成路径是不一致。
//        System.getProperty("user.dir")
        ///Users/jianma/Documents/springcode/springbootmodulenew/springaop-spring-boot-starter/target/classes/org/example/aop/ProjectAdvice.class
        //获取当前i项目系统路径
        File file = new File(".");
        //找到class文件地址
        String canonicalPath = infoProperties.getFilePath();
        byte[] bytes = ctClass.toBytecode();
        //写入到文件夹 --->这样就可以修改编译之后的class文件了,直接把我们的新的字节码文件给放到了定义的哪个目标位置
        FileOutputStream fileOutputStream = new FileOutputStream(canonicalPath);
        fileOutputStream.write(bytes);
        fileOutputStream.close();
        //使用反射进行实例化对象
        Class<ProjectAdvice> projectAdvice = ProjectAdvice.class;
        //得到方法
        Method aspect = projectAdvice.getMethod("aspect");
        Pointcut pointcut = aspect.getAnnotation(Pointcut.class);
        String value = pointcut.value();
        log.info("日志记录拦截包：" + value);
        Constructor<ProjectAdvice> constructor = projectAdvice.getConstructor();
        //实例化对象
        ProjectAdvice projectAdvicebean = constructor.newInstance();
        return projectAdvicebean;
    }

    //设置注解到对应的方法上
    public void saveAnnotation(ConstPool constPool, MethodInfo methodInfo, String typeName, String key, String value) {
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        //这个必须还是输入全类名
        Annotation annotation = new Annotation(typeName, constPool);
        annotation.addMemberValue(key, new StringMemberValue(value, constPool));
        annotationsAttribute.addAnnotation(annotation);
        methodInfo.addAttribute(annotationsAttribute);
    }

}
